昨天才剛把專案調整為 MVP 架構,本來今天想直接改為 MVVM,但突然有點不捨,所以改為解析 Json 字串好了,過幾天在來重構~~
Json 是一種資料交換的格式,由鍵-值對 (key-value) 組成。也就是說需要將取得的 API 回傳資料透過鍵-值將資料解析出來,所以先來看一下這支 API 的欄位說明,這樣才會知道我們要的資料是以哪種 key 儲存 :

試著用 postman 呼叫,可以看到這就是目前的資料長相 :
[
    {
        "id": "00014645-38c8-4eb4-ad9b-faa871d7e511",
        "name": "R5小餐館",
        "city": "chiayi",
        "wifi": 5,
        "seat": 5,
        "quiet": 5,
        "tasty": 5,
        "cheap": 5,
        "music": 5,
        "url": "https://www.facebook.com/r5.bistro",
        "address": "嘉義市東區忠孝路205號",
        "latitude": "23.48386540",
        "longitude": "120.45358340",
        "limited_time": "maybe",
        "socket": "maybe",
        "standing_desk": "no",
        "mrt": "",
        "open_time": "11:30~21:00"
    }
]
這是一個 array object 結構,表示我們可能會收到多筆資料,直接來定義我們的 data 吧!
如果想要儲存資料,Kotlin 有一種特殊的類別 data class,大多是用於儲存資料,先看範例 :
data class Person(val name: String, val age: Int)
定義好的資料結構,Cafe :
data class Cafe(
    val id: String,
    val name: String,
    val city: String,
    val wifi: Int,
    val seat: Int,
    val quiet: Int,
    val tasty: Int,
    val cheap: Int,
    val music: Int,
    val url: String,
    val address: String,
    val latitude: String,
    val longitude: String,
    val limited_time: String,
    val socket: String,
    val standing_desk: String,
    val mrt: String,
    val open_time: String
)
這裡我們用 Gson library 來幫我們解析,省去一個一個把他取出來的步驟,所以先在 Gradle(Module) 加入:
// Gson
implementation 'com.google.code.gson:gson:2.9.0'
將 MainModel  callback 的回傳格式由 String? 改為 List :
override suspend fun getCoffeeShops(city: String?, callback: BaseContract.BaseCallback<List<Cafe>>) {}

接著調整 MainModel 解析資料的程式碼 :
import com.helloiris.taoyuan.findyourcoffee.model.Cafe
// 建立回傳的資料
val cafeList = mutableListOf<Cafe>()
// 解析資料
for (i in 0 until jsonArray.length()) {
    val jsonObject = jsonArray.getJSONObject(i)
    val id = jsonObject.getString("id")
    val name = jsonObject.getString("name")
    val city = jsonObject.getString("city")
    val wifi = jsonObject.getInt("wifi")
    val seat = jsonObject.getInt("seat")
    val quiet = jsonObject.getInt("quiet")
    val tasty = jsonObject.getInt("tasty")
    val cheap = jsonObject.getInt("cheap")
    val music = jsonObject.getInt("music")
    val url = jsonObject.getString("url")
    val address = jsonObject.getString("address")
    val latitude = jsonObject.getString("latitude")
    val longitude = jsonObject.getString("longitude")
    val limitedTime = jsonObject.getString("limited_time")
    val socket = jsonObject.getString("socket")
    val standingDesk = jsonObject.getString("standing_desk")
    val mrt = jsonObject.getString("mrt")
    val openTime = jsonObject.getString("open_time")
    val cafe = Cafe(
        id, name, city, wifi, seat, quiet, tasty, cheap, music, url, address,
        latitude, longitude, limitedTime, socket, standingDesk, mrt, openTime
    )
    cafeList.add(cafe)
}
// 回傳結果
callback.onSuccess(cafeList)
}
catch (e: JSONException) {
    e.printStackTrace()
    callback.onFail(e.message)
}
MainModel :
class MainModel: MainContract.Model {
    override suspend fun getCoffeeShops(city: String?, callback: BaseContract.BaseCallback<List<Cafe>>) {
        withContext(Dispatchers.IO) {
            // 創建一個 OkHttpClient 實例
            val client = OkHttpClient()
            // 設置要發送的 HTTP 請求
            val request = Request.Builder()
                .url("http://cafenomad.tw/api/v1.2/cafes/$city")
                .build()
            val response = try {
                // 使用 OkHttpClient 發送同步請求
                client.newCall(request).execute()
            }
            catch (cause: IOException) {
                callback.onFail(cause.message)
                return@withContext
            }
            if (response.isSuccessful) {
                val json = response.body?.string()
                try {
                    // 建立 JsonArray
                    val jsonArray = JSONArray(json)
                    // 建立回傳的資料
                    val cafeList = mutableListOf<Cafe>()
                    // 解析資料
                    for (i in 0 until jsonArray.length()) {
                        val jsonObject = jsonArray.getJSONObject(i)
                        val id = jsonObject.getString("id")
                        val name = jsonObject.getString("name")
                        val city = jsonObject.getString("city")
                        val wifi = jsonObject.getInt("wifi")
                        val seat = jsonObject.getInt("seat")
                        val quiet = jsonObject.getInt("quiet")
                        val tasty = jsonObject.getInt("tasty")
                        val cheap = jsonObject.getInt("cheap")
                        val music = jsonObject.getInt("music")
                        val url = jsonObject.getString("url")
                        val address = jsonObject.getString("address")
                        val latitude = jsonObject.getString("latitude")
                        val longitude = jsonObject.getString("longitude")
                        val limitedTime = jsonObject.getString("limited_time")
                        val socket = jsonObject.getString("socket")
                        val standingDesk = jsonObject.getString("standing_desk")
                        val mrt = jsonObject.getString("mrt")
                        val openTime = jsonObject.getString("open_time")
                        val cafe = Cafe(
                            id, name, city, wifi, seat, quiet, tasty, cheap, music, url, address,
                            latitude, longitude, limitedTime, socket, standingDesk, mrt, openTime
                        )
                        cafeList.add(cafe)
                    }
                    // 回傳結果
                    callback.onSuccess(cafeList)
                }
                catch (e: JSONException) {
                    e.printStackTrace()
                    callback.onFail(e.message)
                }
            }
            else {
                callback.onFail("Unable to refresh data")
            }
        }
    }
}
這邊也是調整 callback 的回傳值 :
class MainPresenter(iModel: MainContract.Model, iView: MainContract.View) : MainContract.Presenter {
    private val iModel: MainContract.Model
    private val iView: MainContract.View
    init {
        this.iModel = iModel
        this.iView = iView
    }
    override suspend fun getCoffeeShops(city: String?) {
        iModel.getCoffeeShops(city, object : BaseContract.BaseCallback<List<Cafe>?> {
            override fun onFail(errMsg: String?) {
                iView.showFail(errMsg)
            }
            override fun onSuccess(cafes: List<Cafe>?) {
                iView.showCoffeeShop(cafes)
            }
        })
    }
}
最後來到 MainActivity,我們本來在成功後是顯示全部的資料,先改成顯示第一筆資料就好 :
override fun showCoffeeShop(cafes: List<Cafe>) {
    runOnUiThread {
        if (cafes.isNotEmpty()) {
            val cafe = cafes[0]
            binding.textView.text = cafe.toString()
        }
        binding.progressbar.visibility = View.GONE
    }
}
調整完後就來運行一下,這樣有沒有清楚多了~~
明天我們在用 RecyclerView 重新呈現資料,就會更整齊一點!
https://github.com/helloiris0216/IThome15th_pic/blob/master/day15/d15_3.png?raw=truehttps://prod-files-secure.s3.us-west-2.amazonaws.com/caa54e63-1695-4891-b6b8-caed035265d4/9b37d9ae-e999-44bd-a6c9-ccb7e2efa2d5/d15_3.png)
中秋節快樂呀~大家~~
公司的舊專案幾乎都用 for (i in 0 until jsonArray.length()) {} 去逐一解析 json,看久了會有想重構的念頭 XD
XDDD前面加了 Gson,結果後面接資料還忘記用了XDD